import Dep, { pushTarget, popTarget } from './dep';
let id = 0;

class Watcher {
  constructor(vm, exprOrFn, option, cb) {
    this.id = id++;
    this.isRenderWatcher = option;

    if (typeof exprOrFn === 'string') {
      this.getter = function () {
        return vm[exprOrFn];
      };
    } else {
      this.getter = exprOrFn;
    }

    this.deps = [];
    this.depIds = new Set();
    this.lazy = option.lazy;
    this.dirty = this.lazy;
    this.value = this.lazy ? undefined : this.get();
    this.vm = vm;
    this.cb = cb;
    this.user = option.user;
  }

  addDep(dep) {
    const id = dep.id;
    if (!this.depIds.has(id)) {
      this.deps.push(dep);
      this.depIds.add(id);
      dep.addSub(this);
    }
  }

  evaluate() {
    this.value = this.get();
    this.dirty = false;
  }

  // get 的职责：收集依赖并执行 new watcher 实例时 参数中传入的更新操作函数--exprOrFn
  get() {
    pushTarget(this);
    const value = this.getter.call(this.vm); // 这里会触发 用到的属性 的 get，如 用到了 this.age,age 会创建 dep 将当前计算属性 watcher push 到 subs 中，表示 age 依赖当前计算属性 watcher
    popTarget();
    return value;
  }

  depend() {
    for (let i = 0; i < this.deps.length; i++) {
      this.deps[i].depend(); // 让计算属性 dep 收集渲染 watcher
    }
  }

  update() {
    if (this.lazy) {
      this.dirty = true;
    } else {
      // create watcher update queue，循环队列中的每个 watcher 执行 run 函数
      queueWatcher(this);
    }
  }

  // 职责：调用实例的 get 函数
  run() {
    let newValue = this.get();
    if (this.user) {
      this.cb.call(this.vm, newValue, this.value);
    }
  }
}

let queue = [];
let has = {};
let pending = false;

function flushSchedulerQueue() {
  let flushQueue = queue.slice(0);
  queue = [];
  has = {};
  pending = false;
  flushQueue.forEach((q) => q.run());
}

function queueWatcher(watcher) {
  let id = watcher.id;
  if (!has[id]) {
    queue.push(watcher);
    has[id] = true; // 表示当前 watcher 已入队, 同一 watcher 多个属性变化时，该 watcher 只入队一次
    if (!pending) {
      // 等所有 watcher 都入队完毕，即所有数据更新完毕，遍历每个 watcher，进行 render。
      // 直接 setTimeout ，用户获取 dom 时获取的是未更新的数据
      nextTick(flushSchedulerQueue);
      pending = true; // 置为 true 保证只创建了一个 nextTIck 异步任务
    }
  }
}

let callBacks = [];
let waiting = false;

function flushCallBacks() {
  waiting = false;
  let cbs = callBacks.splice(0);
  callBacks = [];
  cbs.forEach((cb) => cb());
}

// setTimeout 优雅降级  Promise -> MutationObserver ->  setImmediate  -> setTimeout
let timerFunc;

if (Promise) {
  timerFunc = (cb) => {
    Promise.resolve().then(cb);
  };
} else if (MutationObserver) {
  let observer = new MutationObserver(flushCallBacks);
  let textNode = document.createTextNode(1);
  observer.observe(textNode, {
    characterData: true,
  });
  timerFunc = (cb) => {
    textNode.textContent = 2;
  };
} else if (setImmediate) {
  timerFunc = (cb) => {
    setImmediate(cb);
  };
} else {
  timerFunc = (cb) => {
    setTimeout(cb);
  };
}

// 用户写的 nextTick 回调任务和 vm 数据变化的回调任务（flushSchedulerQueue）维护到队列
export function nextTick(cb) {
  // setTimeout(cb, 0);
  callBacks.push(cb);
  if (!waiting) {
    timerFunc(flushCallBacks);
    // setTimeout(() => {
    //   flushCallBacks(); // 最后一起执行
    // }, 0);
    waiting = true;
  }
}

export default Watcher;
